package com.smsc.headend.connector.netty.handler;


import cn.hutool.core.date.SystemClock;
import com.smsc.headend.connector.bo.ConnectTaskResultDto;
import com.smsc.headend.connector.bo.PacketHandleResultBO;
import com.smsc.headend.connector.bo.RetryBo;
import com.smsc.headend.connector.builder.LoggerBuilder;
import com.smsc.headend.connector.enums.states.ChannelState;
import com.smsc.headend.connector.manager.TCPClientManager;
import com.smsc.headend.connector.netty.NettyConfigHelper;
import com.smsc.headend.connector.service.DeviceConnectService;
import com.smsc.headend.connector.service.protocol.AnsiPacketCodecService;
import com.smsc.headend.connector.utils.LogFileNameUtil;
import com.smsc.headend.connector.utils.NetUtil;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.protocol.ansi.AnsiTranslator;
import com.smsc.headend.module.protocol.ansi.objects.Acse;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.task.dto.ConnectorTask;
import com.smsc.headend.module.task.dto.ConnectorTaskResult;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode.TASK_WAITING_CON_RESPONSE_OVERTIME;
import static com.smsc.headend.connector.service.impl.DeviceConnectServiceImpl.CHANNEL;
import static io.netty.handler.timeout.IdleState.ALL_IDLE;

@Slf4j
@ChannelHandler.Sharable
public class AnsiPacketHandler extends SimpleChannelInboundHandler {
    @Autowired
    LogFileNameUtil logFileNameUtil;

    @Autowired
    DeviceConnectService deviceConnectService;

    @Autowired
    TCPClientManager tcpClientManager;

    @Autowired
    @Qualifier("retryService")
    ScheduledExecutorService scheduledExecutorService;

    @Value("${packet.resend.timeout:120}")
    Integer packetTimeout;

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        Channel channel = channelHandlerContext.channel();
        ComEntity comEntity = channel.attr(NettyConfigHelper.comEntityAttrKey).get();
        String netAddress = NetUtil.getIpFromChannel(channel);
        MDC.put(CHANNEL, netAddress);
        ByteBuf buf = (ByteBuf) o;
        byte[] result = new byte[buf.readableBytes()];
        buf.readBytes(result);
        if (comEntity != null) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(comEntity), this.getClass()).info("{}: <--{}", netAddress, ByteUtils.byteToHexString(result));
        }
        deviceConnectService.ansiPacketAccept(channel, result);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        ctx.channel().attr(NettyConfigHelper.channelState).setIfAbsent(ChannelState.Init);
        NettyConfigHelper.group.add(ctx.channel());
        super.channelActive(ctx);
    }


    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.channel().attr(NettyConfigHelper.channelState).setIfAbsent(ChannelState.Init);
        super.channelRegistered(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            switch (event.state()) {
                case READER_IDLE:
                    break;
                case WRITER_IDLE:
                    ConnectorTask connectorTask = ctx.channel().attr(NettyConfigHelper.connectTaskRegular).getAndSet(null);
                    if (ctx.channel().attr(NettyConfigHelper.retryFuture).get() != null || connectorTask == null) {
                        break;
                    }
                    ComEntity comEntity = ctx.channel().attr(NettyConfigHelper.comEntityAttrKey).get();
                    ScheduledFuture<?> schedule = scheduledExecutorService.schedule(() -> {
                        taskOverTime(ctx, comEntity);
                    }, packetTimeout - 15, TimeUnit.SECONDS);
                    ctx.channel().attr(NettyConfigHelper.retryFuture).set(schedule);
                case ALL_IDLE:
                    break;
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    private void taskOverTime(ChannelHandlerContext ctx, ComEntity com) {
        ConnectorTask connectorTask = ctx.channel().attr(NettyConfigHelper.connectTaskRegular).getAndSet(null);
        if (connectorTask == null) {
            return;
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(com), this.getClass()).info("taskTimeOver:comId:{},taskNo:{}", com != null ? com.getComId() : "annoy-device", connectorTask.getTaskNo());
        deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, TASK_WAITING_CON_RESPONSE_OVERTIME.toString(), com.getComId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), com);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ComEntity com = ctx.channel().attr(NettyConfigHelper.comEntityAttrKey).get();
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(com), this.getClass()).info("ansiChannelInactive:comId:{},addr:{}", com != null ? com.getComId() : "annoy-device", NetUtil.getIpFromChannel(ctx.channel()));
        tcpClientManager.channelInactive(ctx.channel());
        deviceConnectService.unlockComEntity(ctx.channel());
        NettyConfigHelper.group.remove(ctx.channel());
        super.channelInactive(ctx);
    }
}
